package multi_thread.advanced;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Cheng Jun
 * Description: volatile 只能保证可见性、有序性，不能保证原子性。
 * 被volatile修饰的变量
 * 线程读取该变量时会从主内存中读取(依靠内存屏障LoadFence)，线程本地内存中没有副本。
 * 修改时会直接写入主内存(依靠内存屏障storeFence)，然后才继续运行
 * volatile 在多线程中应用情况
 * 1. 一个线程修改变量，其它线程读取变量，
 * 2. 如果是多个线程修改就需要使用CAS去保证原子性，否则，多个线程一起修改就会出现并发安全问题。
 * @version 1.0
 * @date 2020/3/3 16:32
 */
public class VolatileExample {
    public static void main(String[] args) throws Exception {
        VolatileBoolean volatileBoolean = new VolatileBoolean();
        new Thread(() -> volatileBoolean.get(), "GetThread").start();
        // 确保get方法先执行
        Thread.sleep(1000);
        new Thread(() -> volatileBoolean.add(), "AddThread").start();
    }

}

class VolatileBoolean {
    // 如果把 volatile注释掉 get方法一直会死循环，因为get方法拿到了 flag的副本是false，并存在GetThread线程的内存中。
    // 这样一来，主内存里面flag发生了变化，GetThread线程并不会知道，对GetThread 线程是不可见的。
    private boolean flag = false;

    // volatile 是每次都从主内存取值，不会在线程内存中存放副本，这样就能解决可见性问题
    // private volatile boolean flag = false;

    TestCopy  testCopy = new TestCopy(false);
    private List<Integer> dataList = new ArrayList<>();

    public void add() {
        for (int i = 0; i < 1000; i++) {
            dataList.add(i);
            System.out.println("添加元素i" + i + "成功！");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (dataList.size() == 5) {
                flag = true;
                // testCopy.setFlag(true);
                System.out.println(Thread.currentThread().getName() + " 发出通知 flag为true了");
                break;
            }
        }
    }

    public void get() {
        while (true) {
            if (flag) {
            // if (testCopy.getFlag()) {
                System.out.println(Thread.currentThread().getName() + " 收到通知！");
                break;
            }else {
                // try {
                //     Thread.sleep(1000);
                // } catch (InterruptedException e) {
                //     e.printStackTrace();
                // }
                // 为什么睡眠一会儿，这样会重新load主内存的数据。这个不一定每次睡眠结束后都会获取共享内存，有可能获取而已
                // 不睡眠也会，经过四五秒后也会重新load主内存数据。那是因为 println 方法里面有synchronized，导致输出时会重新加载主内存数据
                // 但是如果 else 里面，什么都不写就永远卡在死循环里面。
                System.out.println("没收到通知，flag = " + flag);
                // sleep前不一定会将工作内存更新到主内存，sleep后也不一定会重新load，只有volatile才是最可靠的
                // https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html Sleep and Yield
                // It is important to note that neither Thread.sleep nor Thread.yield have any synchronization semantics.
                // In particular, the compiler does not have to flush writes cached in registers out to shared memory before a call to Thread.sleep or Thread.yield,
                // nor does the compiler have to reload values cached in registers after a call to Thread.sleep or Thread.yield.
            }
        }
    }
}

// 线程方法区 深度拷贝 主内存的副本
class TestCopy{
    private Boolean flag;

    public TestCopy(boolean flag) {
        this.flag = flag;
    }

    public boolean getFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}